obs(cloud): dedupe redactEmail, classify mailer + startup logs - #364
Merged
Conversation
Follow-ups to #363, found while verifying that PR in production. - redactEmail existed twice with different behavior: src/log.ts kept 1 char via indexOf, src/web/mailer.ts:74 kept 2 via lastIndexOf and also handled a trailing "@". The stricter one is now the only one — it moves into log.ts beside the other redactors (so the dependency runs web → log, not the reverse) and mailer.ts re-exports it for its own callers. - mailer.ts's four log sites were still bare console.error, so a successful send filed at the same level as a failed one: outcome=success is INFO, skipped_no_key is WARNING, both send_failed paths are ERROR. Recipients were already redacted; that is unchanged. - Startup path had no severity at all on Cloud Run — cli.ts's two serve lines, soul/git.ts's git-missing warning, and entrypoint.sh's three echoes. The shell ones get a log_line helper mirroring src/log.ts: JSON under K_SERVICE, plain text otherwise. Verified: /health-serving revision 00021 shows [sweep]/[orchestrator] at INFO and reviewer email redacted, so this only covers what #363 missed. npm test 1637 pass / 0 fail; typecheck + api-contract clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oratis
added a commit
that referenced
this pull request
Aug 21, 2026
The >$10/day/user anomaly alert deduped through a process-local Set, so
with MAX_INSTANCES>1 the same day paged the operator once per instance,
and a cold start re-armed it. The Set stays as the in-process fast path;
the cross-instance arbiter is now a create-only Firestore write on
lisa-anomaly-alerts/{uid}_{day} — the second writer fails its
precondition, and that failure is the dedup signal.
Deliberately fail-open: only 409/412 counts as "someone else has it".
Any other failure (network, permission, Firestore down) still alerts,
because a duplicate alert is a nuisance while a missed one is an
unnoticed burn. With Firestore off or outside a per-uid scope (Mac
edition, shared-token demo) the claim is a no-op and behavior is
unchanged — verified by two tests that assert the network is never hit.
Also finishes this file's share of #363/#364: both console.error sites
become logError, and the alert text names a cloud tenant by redacted uid
instead of embedding the home path (which contains the raw uid). The
"[billing] ⚠ anomaly" prefix the alert policy matches is untouched, and
that policy already matches both textPayload and jsonPayload, so the
move to structured output does not break it.
npm test 1643 pass / 0 fail; typecheck + api-contract clean.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#363 的三个收尾项——都是在生产验收那个 PR 时查出来的(见下方「怎么发现的」)。纯日志卫生,无行为变更。
1.
redactEmail有两份实现,且行为不一致alice.smith@example.comtrailing@src/log.ts(#363 新加)a***@example.coma***@(泄漏原文)src/web/mailer.ts:74(早已存在)al***@example.com***是我在 #363 里没查重引入的重复。保留更严谨的 B(
lastIndexOf+ 处理@结尾),把它移进log.ts与其它脱敏函数放一起——依赖方向因此是web/mailer → log而不是反过来;mailer.ts重新导出,其既有调用方和mailer.test.ts的 5 个 case 不受影响。2.
mailer.ts四处 console.error 未分级发信成功和发信失败此前记在同一级别。现在:
outcome=success→ INFO,skipped_no_key→ WARNING,两条send_failed→ ERROR。收件人本来就是脱敏的(redactEmail(to)),这点没动。3. 启动路径完全没有 severity
cli.ts两行 serve 日志、soul/git.ts的 git-missing 警告、entrypoint.sh三处 echo。前两者走 logger;shell 那三处加了个log_linehelper,逻辑与src/log.ts一致——K_SERVICE下输出单行 JSON,否则纯文本(消息是固定串、无引号无反斜杠,不需要 JSON 转义)。怎么发现的
在生产核实 #363 时按 revision 分组读日志,发现新旧代码的分界线是
00020(reviewer@meetlisa.ai→r***@meetlisa.ai (em-2…b215))。当前00021承 100% 流量、[sweep]/[orchestrator]已是 INFO——但同一批日志里仍有 4 条 severity 为空,顺藤摸到上面这些。验证
npm test1637 pass / 0 fail;typecheck、check:api-contract均干净。log.test.ts的 redactEmail 用例已按更严格的契约更新(含trailing@这个原先会泄漏的 case)。sh -n deploy/entrypoint.sh通过;log_line两种模式实测:K_SERVICE下输出{"severity":"INFO","message":"[cloud] soul already present — skipping birth"},无K_SERVICE时输出原纯文本。console.error,所以mailer.test.ts里捕获日志的既有测试无需改动。不在本 PR 范围
src/billing/meter.ts:114仍是裸 console.error(anomaly 告警因此要匹配 textPayload,云端已用双写法兜住)。MAX_INSTANCES>1会重复告警,需共享态)。src/cli/*的 console 调用是终端给用户看的输出,不应改成结构化日志。🤖 Generated with Claude Code